Skip to content

docs(qa): invert four more activation-ledger read-backs to key-set assertions - #15359

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-15253-more-activation-ledger-checklist-items
Sep 4, 2026
Merged

docs(qa): invert four more activation-ledger read-backs to key-set assertions#15359
hotlong merged 1 commit into
mainfrom
claude/issue-15253-more-activation-ledger-checklist-items

Conversation

@hotlong

@hotlong hotlong commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15253

sys_metadata_activation dropped its reserved organization_id column before it ever
shipped (#15024, in 17.3.0): the object declares systemFields: { tenant: false } — the
opt-out that REMOVES the column rather than leaving it unwritten, since
resolveInjectedSystemColumns would otherwise provision it by injection with no field
declared at all. PR #15251 re-grounded the two items #15154 named. Four siblings still read
that column back.

The population, verified here rather than taken from the card

The card named four items. I re-derived the population on origin/main (c64e65f, which
already carries PR #15251) instead of trusting that list, and the card's four are exactly
right — but the file-level counts in the hold comment are line counts, not occurrence
counts, so they do not add up against a per-item triage. Both numbers, per file, and the
item-level split:

area file lines carrying organization_id occurrences items carrying it
access-security.json 11 13 3 — one repaired by PR #15251 (activation-write-operator-gate, rev 2), two unrelated to the ledger
api-backend.json 4 4 2 — both in scope
automation.json 3 3 1 — in scope
platform-core.json 12 19 2 — one repaired by PR #15251 (activation-ledger-row-contract, rev 2), one in scope
studio-authoring.json 0 0 names the table, never the column
FOLLOW-UPS.md 0 0 names the table, never the column

Triaged item by item, eight items across the family spell organization_id. Four are the
card's four. Two were repaired by PR #15251. The remaining two —
access-security.public-form-intake (6) and access-security.no-active-org-session-semantics
(1) — are about ordinary tenant-stamped records and HTTP-created rows, not this ledger, and
are correct as they stand. Two more sites outside the four but inside the same wording class
are also fixed, both in items already in scope: automated.ref on each api-backend item
described the dogfood pin with the old spelling.

So: four items, three files, no fifth item found, and nothing outside the defect class
touched.

Why this is not a value swap

These legs were vacuous, not merely wrong. They read organization_id back and asserted
it NULL. With the column gone, a value check of the shape row.organization_id ?? null
answers null whether or not the column exists — the read passes instead of failing, so
a runner sees green for exactly the reason it should be red. Every one is inverted into a
key-set assertion (the column is ABSENT from the row's keys), which is how #15024 fixed
the dogfood pin and how PR #15251 fixed step 2 of the row-contract item.

Two of the four were also wrong on the decision, not just the value. They glossed the
column as reserved and unwritten, citing ADR-0126 §5 D3 — a bullet ADR-0131 D7 withdrew
("a reserved nullable tenant column is the shape D1 forbids"), naming this table as reverted
before 17.3 and not returning. Left as they stood they told a runner the tenant dimension was
coming back. Both now cite ADR-0131 D7 and mark which half of §5 survives: the operator
gate stands, the reserved column does not.

What changed, per item

  • platform-core.activation-ledger-registration-home (rev 1 → 2) — step 2's row capture
    and the no-automation clause's "ONE install-level row (organization_id NULL)". Re-said as
    DEPLOYMENT-level: with no tenant column there is no second scope for install-level to be
    distinguished from. The schema-side probe stays with the sibling item that owns it
    (platform-core.activation-ledger-row-contract), cited rather than restated. ADR-0131 D7
    added to source.
  • api-backend.packaged-action-disabled-dispatch (rev 1 → 2) — the disable step's row
    confirmation, plus automated.ref, which described the dogfood pin as asserting "ONE
    install-level row with organization_id NULL". That pin has been a key-set reading named
    writes ONE deployment-level row, and the TABLE has no tenant column at all since fix(platform-objects,core): sys_metadata_activation ships tenant-less — drop the reserved organization_id before 17.3 is cut (ADR-0126 amended by ADR-0131 D6/D7) #15024;
    the ref now quotes its live name, verified against
    packages/qa/dogfood/test/packaged-activation-ledger-reach.dogfood.test.ts.
  • api-backend.action-activation-door-contract (rev 1 → 2) — the happy-path step, its
    acceptance clause (which carried the "reserved and unwritten" gloss) and automated.ref
    ("one org-NULL row"). source gains ADR-0131 D7 and an annotation on the ADR-0126 line
    saying which half of §5 survives.
  • automation.packaged-flow-disable-durable (rev 1 → 2) — the ledger read step (which
    listed organization_id among the fields to capture), the row clause and its "§5 reserves
    the column" gloss, the evidence line's "all five columns", the verify line's NULL-collapsed
    'organization' index, and the source line still citing "org-carrying rows skipped on
    read" plus "organization_id never written". Grounded against source: the index is a plain
    unique: 'global' over two required key parts (with no tenant column
    normalizeDeclaredIndex has no part to prepend, so there is no COALESCE arm to reach), and
    the read-side skip is retired — list() is scoped by metadata_type and by nothing else.
    The store citation splits into #setActive and #list so the read-path correction sits
    where the read path lives.

Each bumps revision and appends a history entry, per the checklist's change lifecycle
(docs/qa/platform-checklist/README.md). No clause of the four contracts themselves moved —
only what a runner records about the row.

Gates

pnpm check:platform-checklist is deliberately not in the lint workflow, so it was run by
hand, before any edit and again on the final commit.

BEFORE (origin/main c64e65fb1)   exit 1 · check-platform-checklist: 5 problem(s)
AFTER  (41afe8c70)               exit 1 · check-platform-checklist: 5 problem(s)

Same five, byte for byte — batch_endpoints, crud_endpoints, metadata_endpoints,
realtime_subscription, route_generation, all coverage.json · UNCLASSIFIED. None added,
none removed.

⚠️ Correction to the dispatch: the pre-existing UNCLASSIFIED set is five, not four.
The card and the hold comment both name four (batch_endpoints, crud_endpoints,
metadata_endpoints, route_generation) and omit realtime_subscription, which is present
on origin/main before this branch exists. Measured, not recalled.

Symbol-anchor floor — non-shrinking, and not lowered.
scripts/checklist-symbol-anchor-baseline.json is untouched by this PR (its last commit is
still #15159). Census, node scripts/check-platform-checklist.mjs --anchor-census:

before: 612/612 resolved · areas/automation.json 51 (floor 51) · areas/api-backend.json 41 (floor 41) · areas/platform-core.json 60 (floor 58)
after:  613/613 resolved · areas/automation.json 52 (floor 51) · areas/api-backend.json 41 (floor 41) · areas/platform-core.json 60 (floor 58)

The one movement is upward: splitting the automation item's store citation adds
packages/core/src/utils/metadata-activation-store.ts#list, and it resolves. Every other
family file is unchanged.

Derived gate family. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
let the script derive its own change set (3 paths vs merge base c64e65f) and named 10
runnable families. All 10 re-run on the final commit 41afe8c, all exit 0:

[0] node scripts/check-ci-filter-parity.mjs
[0] node scripts/check-closing-keyword-parity.mjs
[0] node scripts/check-closing-keyword-parity.mjs --self-test
[0] node scripts/check-comment-mask-corpus.mjs
[0] pnpm --filter @objectstack/lint run check:doc-formula-expressions
[0] pnpm check:cross-package-test-inputs
[0] pnpm check:doc-authoring
[0] pnpm check:nul-bytes
[0] pnpm check:refd-timer-probe
[0] pnpm check:watch-hint-literal

Exit codes captured by redirect-then-read, never across a pipe. One NOT MEASURED result
worth recording rather than hiding: check:doc-formula-expressions first answered exit 3 —
PREREQUISITE NOT MET
, twice in sequence (@objectstack/formula unbuilt, then
@objectstack/lint unbuilt). Exit 3 is that gate's own "nothing was measured" code, distinct
from a finding's 1; it is not a red and not a green. Building both packages
(turbo run build --filter=...) turned it into a real measurement, and the verdict lines it
then printed are the ones cited above (58 self-test cases; 22 record-scoped examples across
427 files; 9 spec TSDoc examples; 14 field-level predicates).

Nothing else was NOT MEASURED. check:platform-checklist is a real red-by-design on
origin/main and stays at the identical five.

Verify-lock declaration. The two builds went through
bash scripts/pm/os-verify-lock.sh -c '...', which reported
UNLOCKED (declared) · no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized. Declaring it here as the script's own output instructs;
neither build is a measurement this PR rests on.

Anti-vacuity ablation

A checklist edit's gate can go green by never reading the edited file. Driven against the one
anchor this change adds — the new #list citation in areas/automation.json — re-pointed
at a symbol that does not exist:

HEAD blob for docs/qa/platform-checklist/areas/automation.json = bf3d48db9fbf96907dcb4327f69b78a6fbcaad66
on-disk  before   = bf3d48db9fbf96907dcb4327f69b78a6fbcaad66
pre-mutation  counts: original=1  injected=0

BASELINE (committed tree)   exit=1  problems=5

MUTATE
post-mutation counts: original=0  injected=1
on-disk after mutate = 2a07833047a28c2f2eacf377ca23b58e303dc90a

MUTATED RUN                 exit=1  problems=6
  the sixth: areas/automation.json: ABSENT SYMBOL --
  packages/core/src/utils/metadata-activation-store.ts#qaAblationNoSuchSymbol is not in
  packages/core/src/utils/metadata-activation-store.ts outside its comments

RESTORE
on-disk after restore = bf3d48db9fbf96907dcb4327f69b78a6fbcaad66   (== HEAD blob)
git diff HEAD --stat  : empty
git status --porcelain: clean
post-restore  counts: original=1  injected=0

POST-RESTORE RUN            exit=1  problems=5   (the same five)

The mutation is confirmed on disk by occurrence counts on both the removed and the injected
text and by a changed blob hash — never by the editor's exit code. The restore is proved
by git hash-object equalling the HEAD blob plus an empty git diff HEAD and a clean
git status, not by the restore command's exit code. The script carried
trap restore EXIT INT TERM with absolute paths resolved from git rev-parse --show-toplevel.

⚠️ The first attempt was a measured NO-OP and is reported rather than quietly re-run into
existence.
The mutation used perl -0pi -e with the em dash spelled \x{2014} inside a
\Q...\E block, which quotes the escape's own backslash — so perl searched for a literal
that is not in the file, exited 0, and left the blob hash unchanged. The on-disk
occurrence check caught it (original=1 injected=0 after the mutation) and the run aborted
before reading a single gate number. Had the reading been taken from the exit code, this
ablation would have reported a healthy-looking mutation over an unmodified file. The anchor
was re-spelled in ASCII and the run above is the real one.

Scope

Three files, all under docs/qa/platform-checklist/areas/. No source file, nothing under
content/docs/releases/, no checklist item that does not carry this defect, and
scripts/checklist-symbol-anchor-baseline.json untouched.

Changeset: none — docs/QA only, publishing nothing from any released package, so the
skip-changeset label applies. ⛔ Deliberately not an empty-frontmatter changeset (#4898).

Not a governed surface: the diff is docs/qa/** only, so GOVERNED_SURFACES is not hit.
Auto-merge is not armed and the PR is not queued — the PM enqueues.

Refs: #15154 · PR #15251 · #15024 · ADR-0131 D7 · ADR-0126 §5


Generated by Claude Code

…sertions

`sys_metadata_activation` dropped its reserved `organization_id` column before it
ever shipped (#15024): the object declares `systemFields: { tenant: false }`, the
opt-out that REMOVES the column rather than leaving it unwritten. PR #15251
re-grounded the two items #15154 named. Four siblings still read that column back
and asserted it NULL — a VACUOUS leg, not merely a wrong value: once the column
is gone, `row.organization_id ?? null` answers `null` either way, so the read
passes rather than failing and a runner sees green for exactly the reason it
should be red. Each is inverted into a KEY-SET assertion, the shape #15024 used
on the dogfood pin and PR #15251 carried into step 2.

- platform-core.activation-ledger-registration-home — step 2's row capture and
  the no-automation clause's "ONE install-level row (organization_id NULL)". The
  row is re-said as DEPLOYMENT-level: with no tenant column there is no second
  scope for install-level to be distinguished from. The schema-side probe stays
  with the sibling item that owns it.
- api-backend.packaged-action-disabled-dispatch — the disable step's row
  confirmation, plus `automated.ref`, which described the dogfood pin as
  asserting "ONE install-level row with organization_id NULL". That pin has been
  a key-set reading named "writes ONE deployment-level row, and the TABLE has no
  tenant column at all" since #15024; the ref now quotes its live name.
- api-backend.action-activation-door-contract — the happy-path step, its
  acceptance clause and `automated.ref`. The clause also glossed the column as
  "the per-org dimension is reserved and unwritten": ADR-0131 D7 WITHDREW that
  ADR-0126 §5 D3 bullet ("a reserved nullable tenant column is the shape D1
  forbids") and names this table as reverted before 17.3 and not returning, so
  the gloss told a runner the tenant dimension was coming back. The source list
  now cites ADR-0131 D7 and marks which half of §5 survives.
- automation.packaged-flow-disable-durable — the ledger read step, the row
  clause and its "§5 reserves the column" gloss, the verify line's
  NULL-collapsed 'organization' index (it is a plain `unique: 'global'` over two
  required key parts; with no tenant column `normalizeDeclaredIndex` has no part
  to prepend), and the `source` line still citing "org-carrying rows skipped on
  read" and "organization_id never written". The store citation splits so
  `#list` carries the read-path correction where the read path lives.

Each item bumps `revision` and appends a `history` entry, per the checklist's
change lifecycle. No item outside this defect class is touched, and no clause of
the four contracts themselves moved — only what a runner records about the row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hotlong hotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 4, 2026
@github-actions github-actions Bot added the size/s label Sep 4, 2026
@hotlong
hotlong added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit a623a15 Sep 4, 2026
35 checks passed
@hotlong
hotlong deleted the claude/issue-15253-more-activation-ledger-checklist-items branch September 4, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(qa): four more platform-checklist items read back organization_id on sys_metadata_activation, which has no such column

1 participant